class SerialFormat
  GENERIC_SERIAL = /(?:\w+\s*-\s*)+\w+/
  attr_reader :format, :regexp;
  
  def initialize(format=nil, *args)
    @format = format || GENERIC_SERIAL;
    @regexp = to_regexp(format, *args);
  end
  
  def test(serial)
    valid = serial =~ @regexp;
    valid = test_generic(serial) if generic? and valid;
    valid;
  end
  
  def test_generic(serial)
    tokens = serial.split("-");
    #valid = tokens.map{|token| token.size;}.uniq.size == 1;
    valid = tokens.all? do |token|
      token =~ /^\w+$/ and token =~ /\d/
    end
    valid;
  end
  
  alias_method :=~, :test;
  
  def generic?
    @regexp == GENERIC_SERIAL;
  end
  
  private
  def to_regexp(format=nil, *args)
    if format.kind_of?(String)
      format = format.upcase;
      {"N" => "\\d", "W" => "\\w", "A" => "[a-zA-Z]"}.each do |letter, regexp|
        format.gsub!(letter, regexp);
      end
      format = Regexp.new(format, *args);
    end
    (format.kind_of?(Regexp)) ? format : GENERIC_SERIAL;
  end
  
end

class Serial
  attr_reader :serial;
  
  def initialize(serial)
    @serial = serial.chomp.downcase.gsub(/\s+/, "");
  end
  
  def serial
    @serial.upcase;
  end
  
  def self.valid?(serial, format=nil)
    self.new(serial).validate(format);
  end
  
  def validate(format=nil)
    format = SerialFormat.new(format) unless format.kind_of?(SerialFormat);
    return_self(format =~ @serial);
  end
  
  def self.find_serials(text, format=nil)
    format = SerialFormat.new(format);
    text.scan(format.regexp).map{|serial| self.new(serial).validate(format);}.compact;
  end
  
  private  
  def return_self(boolean)
    (boolean) ? self : nil;
  end
  
end